Skip to main content
  1. SwiftUI in 100 Days Notes/

Day 52 - SwiftUI Project 10 (Cupcake Corner) Challange and Solutions

Table of Contents

Hopefully this project has shown you how to use the skills you already know (SwiftUI Picker, Stepper and Navigation) and turn them into an app that sends all the user’s data to a server and processes the response.

You may not realize it yet, but the skills you learned in the project are important skills for the vast majority of iOS developers: receiving user data, sending it to a server, and processing the response is probably half of the non-trivial apps that exist. Yes, what data to send and how to use it to update the UI varies greatly, but the concepts are the same.

Challange #

Here are 3 ways we should try to improve this practice;

  1. Our address fields are currently considered valid if they contain anything, even just spaces. Improve validation to make sure that a string containing only spaces is invalid.
  2. Show an informative warning for the user if our placeOrder() call fails - for example if there is no internet connection. To test this, you can comment out the line request.httpMethod = "POST" in your code, this will force the request to fail.
  3. For a more challenging task, try updating the Order class so that we can save data like the user’s delivery address in UserDeafults. This requires some thought, because @AppStorage doesn’t work here and you will find that getters and setters cause problems with Codable support. Can you find a middle ground?

Challange Solutions #

  1. To solve this problem we can write an extension on String. In this extension we can use the allSatisfy() method to check each element of the String. Since each element of String is Character, we can use .isWhitespace, a method defined on Character. With the following code we will add a new method to String and return true if the string is whitespace.

    extension String {
        var isBlank: Bool {
            allSatisfy({$0.isWhitespace})
        }
    }
    

    After writing the code above, in our Order class, we can change var hasValidAddress: Bool { we can change it as follows;

    if name.isEmpty || name.isBlank || streetAddress.isEmpty || streetAddress.isBlank || city.isEmpty || city.isBlank || zip.isEmpty || zip.isBlank {
    
  2. To show a warning when there is a problem with the internet connection, let’s first create a variable in the CheckoutView;

    @State private var showingNoInternet = false
    

    Then let’s create another one under the alert we created before;

    .alert("Oops!", isPresented: $showingNoInternet) {
         Button("OK", role: .cancel) { }
     } message: {
         Text("Please check your internet connection.")
     }
    

    Finally, let’s add the following code to the catch block in our placeOrder() method;

    showingNoInternet = true
    

    no internet alert

  3. To solve this problem, first of all we need to decide where to write the data to UserDefaults. The best place for this is the NavigationLink section in AddressView where we push the CheckoutView. So change the code as follows;

    Section{
        NavigationLink("Check out") {
            CheckoutView(order: order)
                .onAppear {
                    let addressItems = [order.name, order.streetAddress, order.city, order.zip]
                    if let encoded = try? JSONEncoder().encode(addressItems) {
                        UserDefaults.standard.set(encoded, forKey: "addressItems")
                        print("Address Items kaydedildi.")
                    }
                }
        } //NavigationLink
    } //Section-2
    

    The problem now is where to read the data we saved in UserDefaults. The best place for this is the init() function of the Order class. In this function we read UserDefaults and if there is any saved data we assign it to the corresponding values when the class object is created, if there is no data we assign an empty string. To do this, change where we define the following variables in the Order class;

    var name: String
    var streetAddress: String
    var city: String
    var zip: String
    

    Then write the init() function as follows;

    init() {
        if let dataName = UserDefaults.standard.data(forKey: "addressItems") {
            if let decodedName = try? JSONDecoder().decode([String].self, from: dataName) {
                name = decodedName[0]
                streetAddress = decodedName[1]
                city = decodedName[2]
                zip = decodedName[3]
                return
            }
        }
    
        name = ""
        streetAddress = ""
        city = ""
        zip = ""
    }
    

You can access the completed project via the Github link below;


You can also read this article in Turkish.
Bu yazıyı Türkçe olarak da okuyabilirsiniz.

This article contains the notes I took for myself from the articles found at SwiftUI Day 52. Please use the link to follow the original lesson.